package cct.tools;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.StringTokenizer;

import cct.Constants;
import cct.interfaces.AtomInterface;
import cct.interfaces.CoordinateBuilderInterface;
import cct.interfaces.CoordinateParserInterface;
import cct.interfaces.MoleculeInterface;
import cct.modelling.ChemicalElements;

/**
 * <p>Title: Computational Chemistry Tookit</p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2005-2010 Dr. Vladislav Vassiliev</p>
 *
 * <p>Company: ANU</p>
 *
 * @author not attributable
 * @version 1.0
 */
public class XMolXYZ
    implements CoordinateBuilderInterface, CoordinateParserInterface {

   private boolean atomicSymbols = true;
   private boolean printComment = true;
   private boolean printNumberOfAtoms = true;

   public XMolXYZ() {
   }

   public void saveXMolXYZ(Writer writer, MoleculeInterface mol, boolean atomicSymbols, boolean inAngstroms) throws
       Exception {
      if (mol == null || mol.getNumberOfAtoms() < 1) {
         throw new Exception("No atoms in molecule");
      }
      float factor = 1.0f;
      if (!inAngstroms) {
         factor = (float) (1.0 / Constants.ONE_BOHR);
      }

      BufferedWriter out;
      try {
         out = new BufferedWriter(writer);
      }
      catch (Exception ex) {
         throw new Exception("Cannot write :" + ex.getMessage());
      }

      try {
         if (printNumberOfAtoms) {
            out.write(mol.getNumberOfAtoms() + "\n");
         }

         if (printComment) {
            out.write("Generated by JMolEditor, units: ");
            if (inAngstroms) {
               out.write("Angstroms\n");
            }
            else {
               out.write("Bohrs\n");
            }
         }

         for (int i = 0; i < mol.getNumberOfAtoms(); i++) {
            AtomInterface atom = mol.getAtomInterface(i);
            if (atomicSymbols) {
               out.write(String.format("%3s ", ChemicalElements.getElementSymbol(atom.getAtomicNumber())));
            }
            else {
               out.write(String.format("%3d ", atom.getAtomicNumber()));
            }
            out.write(String.format("%12.6f %12.6f %12.6f\n", atom.getX() * factor, atom.getY() * factor, atom.getZ() * factor));
         }

         out.close();
      }
      catch (Exception ex) {
         throw new Exception("Cannot write : " + ex.getMessage());
      }

   }

   public void saveXMolXYZ(String filename, MoleculeInterface mol, boolean atomicSymbols, boolean inAngstroms) throws
       Exception {
      try {
         saveXMolXYZ(new FileWriter(filename), mol, atomicSymbols, inAngstroms);
      }
      catch (Exception ex) {
         throw new Exception("Error saving into file " + filename + " : " + ex.getMessage());
      }

   }

   /**
    * Parses XMol XYZ files
    * 1) The first line contains the number of atoms in the molecule.
    * 2) The second line contains the name or title of the molecule. It can be blank if desired.
    * 3) The rest of the file is composed of one line for each atom in the molecule.
    *    Each atom is designated by the atomic number and then the x, y, and z coordinate value.
    *    Separate each value by at least 1 space; there is no strict format for these lines.
    * @param filename String
    * @param mol MoleculeInterface
    * @return MoleculeInterface
    * @throws Exception
    */
   public MoleculeInterface parseXMolXYZ(String filename, MoleculeInterface mol) throws
       Exception {
      String line;
      BufferedReader in = null;
      try {
         in = new BufferedReader(new FileReader(filename));

         // --- Reading number of atoms
         if ( (line = in.readLine()) == null) {
            in.close();
            throw new Exception(
                "parseXMolXYZ: ERROR: Unxpected End of file while reading number of atoms");
         }

         int natoms = 0;
         try {
            natoms = Integer.parseInt(line.trim());
         }
         catch (Exception e) {
            in.close();
            throw new Exception(
                "parseXMolXYZ: ERROR: Error while parsing number of atoms: " +
                e.getMessage());
         }

         // --- Reading molecule name
         if ( (line = in.readLine()) == null) {
            in.close();
            throw new Exception(
                "parseXMolXYZ: ERROR: Unxpected End of file while reading fragment name");
         }
         mol.setName(line);

         // --- Reading atoms

         for (int i = 0; i < natoms; i++) {

            AtomInterface atom = mol.getNewAtomInstance();

            if ( (line = in.readLine()) == null) {
               in.close();
               throw new Exception("parseXMolXYZ: ERROR: Unxpected End of file while atom " + (i + 1));
            }
            StringTokenizer st = new StringTokenizer(line, " \t", false);
            if (st.countTokens() < 4) {
               in.close();
               throw new Exception(
                   "parseXMolXYZ: ERROR: Error while parsing atom: " + (i + 1) + " Number of tokens < 4: " + line);
            }

            // --- Getting element

            String token = st.nextToken();
            int element = ChemicalElements.getAtomicNumber(token);
            atom.setAtomicNumber(element);
            atom.setName(token);

            // --- Getting x,y,z

            float xyz;
            try {
               xyz = Float.parseFloat(st.nextToken());
               atom.setX(xyz);
               xyz = Float.parseFloat(st.nextToken());
               atom.setY(xyz);
               xyz = Float.parseFloat(st.nextToken());
               atom.setZ(xyz);
            }
            catch (Exception ex) {
               in.close();
               throw new Exception(
                   "parseXMolXYZ: ERROR: Error while parsing atom: " + (i + 1) + " Cannot parse atom's coordinate(s): " + line);
            }

            mol.addAtom(atom);
         }

         in.close();
      }
      catch (Exception ex) {
         if (in != null) {
            in.close();
         }
         throw new Exception("parseXMolXYZ: ERROR: " + ex.getMessage());
      }

      return mol;
   }

   @Override
  public void getCoordinates(MoleculeInterface mol, boolean inAngstroms, Writer writer) throws Exception {
      boolean store_1 = printComment;
      boolean store_2 = printNumberOfAtoms;
      printComment = false;
      printNumberOfAtoms = false;

      saveXMolXYZ(writer, mol, atomicSymbols, inAngstroms);

      printComment = store_1;
      printNumberOfAtoms = store_2;
   }

   @Override
  public String getCoordinatesAsString(MoleculeInterface molec, boolean inAngstroms) throws Exception {
      boolean store_1 = printComment;
      boolean store_2 = printNumberOfAtoms;
      printComment = false;
      printNumberOfAtoms = false;

      StringWriter sw = new StringWriter();
      saveXMolXYZ(sw, molec, atomicSymbols, inAngstroms);

      printComment = store_1;
      printNumberOfAtoms = store_2;

      return sw.toString();
   }

   @Override
  public void parseCoordinates(BufferedReader in, MoleculeInterface molecule) throws Exception {
      try {
         molecule.addMonomer("XMol");
         // --- Reading atoms
         String line;
         while ( (line = in.readLine()) != null) {

            line = line.trim();
            if (line.length() < 1) {
               continue; // Empty line
            }

            AtomInterface atom = molecule.getNewAtomInstance();

            StringTokenizer st = new StringTokenizer(line, " \t", false);
            if (st.countTokens() < 4) {
               throw new Exception("parseXMolXYZ: Expected at least 4 tokens, got: " + line);
            }

            // --- Getting element

            String token = st.nextToken();
            int element = ChemicalElements.getAtomicNumber(token);
            atom.setAtomicNumber(element);
            atom.setName(token);

            // --- Getting x,y,z

            float xyz;
            try {
               xyz = Float.parseFloat(st.nextToken());
               atom.setX(xyz);
               xyz = Float.parseFloat(st.nextToken());
               atom.setY(xyz);
               xyz = Float.parseFloat(st.nextToken());
               atom.setZ(xyz);
            }
            catch (Exception ex) {
               throw new Exception("parseXMolXYZ: Error while parsing atom's coordinate: " + line);
            }

            molecule.addAtom(atom);
         }

      }
      catch (Exception ex) {
         throw ex;
      }

   }

   @Override
  public double evaluateCompliance(BufferedReader in) throws Exception {
      double score = 0;
      try {
         String line;
         while ( (line = in.readLine()) != null) {

            line = line.trim();
            if (line.length() < 1) {
               continue; // Empty line
            }

            score = 1;
            StringTokenizer st = new StringTokenizer(line, " \t", false);
            if (st.countTokens() < 4) {
               return 0;
            }
            else if (st.countTokens() > 4) {
               score -= 0.25;
            }

            // --- Getting element

            st.nextToken();

            // --- Getting x,y,z

            try {
               Float.parseFloat(st.nextToken());
               Float.parseFloat(st.nextToken());
               Float.parseFloat(st.nextToken());
            }
            catch (Exception ex) {
               return 0;
            }
            return score;
         }

      }
      catch (Exception ex) {
         throw ex;
      }
      return score;
   }
}
